package com.eptonic.propmodifier;

import android.app.AndroidAppHelper;
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam;

import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;

public class Modifier implements IXposedHookLoadPackage {

    private enum PrimitiveType {BOOLEAN, BYTE, CHAR, Double, FLOAT, INT, LONG, SHORT}

    public void handleLoadPackage(LoadPackageParam lpp) throws Throwable {
        /** set miscellaneous property below */
        doHook("android.telephony.TelephonyManager", "getDeviceId",
                Def.IMEI, "100021000210002", null, lpp);
        doHook("android.net.wifi.WifiInfo", "getMacAddress",
                Def.MAC, null, null, lpp);
        doHook("android.provider.Settings.Secure", "getString",
                Def.ANDROID_ID, null, null, lpp, ContentResolver.class, String.class);
        doHook("android.telephony.TelephonyManager", "getSimOperator",
                Def.SIM_OPERATOR_NUMERIC, null, null, lpp); // gsm.sim.operator.numeric
        doHook("android.telephony.TelephonyManager", "getSimOperatorName",
                Def.SIM_OPERATOR_NAME, null, null, lpp); // gsm.sim.operator.alpha
        doHook("android.telephony.TelephonyManager", "getSimCountryIso",
                Def.SIM_COUNTRY_ISO, null, null, lpp); // gsm.sim.operator.iso-country
        doHook("android.telephony.TelephonyManager", "getSimState",
                Def.SIM_STATE, null, PrimitiveType.INT, lpp); // gsm.sim.state
        doHook("android.telephony.TelephonyManager", "getCurrentPhoneType",
                Def.PHONE_TYPE, null, PrimitiveType.INT, lpp); // gsm.current.phone-type
        doHook("android.telephony.TelephonyManager", "getNetworkOperatorName",
                Def.NETWORK_OPERATOR_NAME, null, null, lpp); // gsm.operator.alpha
        doHook("android.telephony.TelephonyManager", "getNetworkOperator",
                Def.NETWORK_OPERATOR_NUMERIC, null, null, lpp); // gsm.operator.numeric
        doHook("android.telephony.TelephonyManager", "getNetworkCountryIso",
                Def.NETWORK_COUNTRY_ISO, null, null, lpp); // gsm.operator.iso-country
        doHook("android.telephony.TelephonyManager", "isNetworkRoaming",
                Def.NETWORK_ROAMING, null, PrimitiveType.BOOLEAN, lpp); // gsm.operator.isroaming
        doHook("android.telephony.TelephonyManager", "getNetworkType",
                Def.NETWORK_TYPE, null, PrimitiveType.INT, lpp); // gsm.network.type

        /** set custom static field below, most of items are used for build.prop */
        findAndHookMethod("android.app.Application", lpp.classLoader, "attach", Context.class, new XC_MethodHook() {
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                setBuildPropValue(Build.class, "MODEL", Def.MODEL, param, null);
                setBuildPropValue(Build.class, "ID", Def.BUILD_ID, param, null);
                setBuildPropValue(Build.class, "DISPLAY", Def.DISPLAY_ID, param, null);
                setBuildPropValue(Build.VERSION.class, "INCREMENTAL", Def.VER_INCREM, param, null);
                setBuildPropValue(Build.VERSION.class, "SDK", Def.SDK, param, null);

                setBuildPropValue(Build.VERSION.class, "CODENAME", Def.CODE_NAME, param, null);
                setBuildPropValue(Build.VERSION.class, "RELEASE", Def.RELEASE_VERSION, param, null);
                setBuildPropValue(Build.class, "TIME", Def.DATE_UTC, param, PrimitiveType.LONG);
                setBuildPropValue(Build.class, "TYPE", Def.BUILD_TYPE, param, null);
                setBuildPropValue(Build.class, "USER", Def.USER, param, null);

                setBuildPropValue(Build.class, "HOST", Def.HOST, param, null);
                setBuildPropValue(Build.class, "TAGS", Def.TAGS, param, null);
                setBuildPropValue(Build.class, "BRAND", Def.BRAND, param, null);
                setBuildPropValue(Build.class, "PRODUCT", Def.PRODUTCT_NAME, param, null);
                setBuildPropValue(Build.class, "DEVICE", Def.DEVICE, param, null);

                setBuildPropValue(Build.class, "BOARD", Def.BOARD, param, null);
                setBuildPropValue(Build.class, "CPU_ABI", Def.CPU_ABI, param, null);
                setBuildPropValue(Build.class, "FINGERPRINT", Def.FINGERPRINT, param, null);
            }
        });
    }

    private void setBuildPropValue(Class<?> clazz, String fieldName, String key, XC_MethodHook.MethodHookParam param, PrimitiveType primitiveType) throws PackageManager.NameNotFoundException {
        String loadedStr = loadFromFile((Context) param.args[0], key, "xpose_build_prop_def_val");
        if (primitiveType == null) {
            XposedHelpers.setStaticObjectField(clazz, fieldName, loadedStr);
        } else {
            switch (primitiveType) {
                case BOOLEAN:
                    XposedHelpers.setStaticBooleanField(clazz, fieldName, Boolean.parseBoolean(loadedStr));
                    break;
                case BYTE:
                    XposedHelpers.setStaticByteField(clazz, fieldName, Byte.parseByte(loadedStr));
                    break;
                case Double:
                    XposedHelpers.setStaticDoubleField(clazz, fieldName, Double.parseDouble(loadedStr));
                    break;
                case FLOAT:
                    XposedHelpers.setStaticFloatField(clazz, fieldName, Float.parseFloat(loadedStr));
                    break;
                case INT:
                    XposedHelpers.setStaticIntField(clazz, fieldName, Integer.parseInt(loadedStr));
                    break;
                case LONG:
                    XposedHelpers.setStaticLongField(clazz, fieldName, Long.parseLong(loadedStr));
                    break;
                case SHORT:
                    XposedHelpers.setStaticShortField(clazz, fieldName, Short.parseShort(loadedStr));
                    break;
                default:
                    break; // ignore PrimitiveType.CHAR
            }
        }
    }

    /**
     * @param primitiveType
     * @param args          当方法含有参数时，各参数的.class
     */
    private void doHook(String className, String methodName, final String key, final String defaultValue, final PrimitiveType primitiveType, LoadPackageParam lpp, Object... args) {
        Object[] argsAndCallback = new Object[args.length + 1];
        for (int i = 0; i < args.length; i++) {
            argsAndCallback[i] = args[i];
        }
        argsAndCallback[args.length] = new XC_MethodHook() {
            @Override
            protected void afterHookedMethod(MethodHookParam mhp) throws Throwable {
                String loadedStr = loadFromFile(AndroidAppHelper.currentApplication(),
                        key, (defaultValue == null) ? ("default_" + key) : defaultValue);
                if (primitiveType == null) {
                    mhp.setResult(loadedStr);
                } else {
                    switch (primitiveType) {
                        case SHORT:
                            mhp.setResult(Short.parseShort(loadedStr));
                            break;
                        case LONG:
                            mhp.setResult(Long.parseLong(loadedStr));
                            break;
                        case INT:
                            mhp.setResult(Integer.parseInt(loadedStr));
                            break;
                        case FLOAT:
                            mhp.setResult(Float.parseFloat(loadedStr));
                            break;
                        case BOOLEAN:
                            mhp.setResult(Boolean.parseBoolean(loadedStr));
                            break;
                        case BYTE:
                            mhp.setResult(Byte.parseByte(loadedStr));
                            break;
                        case Double:
                            mhp.setResult(Double.parseDouble(loadedStr));
                            break;
                        default:
                            break; // ignore PrimitiveType.CHAR
                    }
                }
            }
        };
        findAndHookMethod(className, lpp.classLoader, methodName, argsAndCallback);
    }

    private String loadFromFile(Context context, String key, String defaultValue) throws PackageManager.NameNotFoundException {
        return Util.loadFromFileByOurContext(
                context.createPackageContext("com.eptonic.propmodifier", Context.CONTEXT_IGNORE_SECURITY),
                key, defaultValue);
    }
}
